/*
 * Copyright (C) 2010 Fred Barrie This program is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version. This program is distributed
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received
 * a copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package org.aergia.vinny.main;

import java.io.File;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import javax.swing.UIManager;

import org.aergia.vinny.core.ApiResult;
import org.aergia.vinny.core.enums.DayOfWeek;
import org.aergia.vinny.database.Database;
import org.aergia.vinny.database.DatabaseImpl;
import org.aergia.vinny.database.pojo.Location;
import org.aergia.vinny.database.pojo.OperatingHours;
import org.aergia.vinny.database.pojo.Organization;
import org.aergia.vinny.database.pojo.VinnyOption;
import org.aergia.vinny.database.pojo.Workstation;
import org.aergia.vinny.database.util.BackupUtil;
import org.aergia.vinny.engine.Engine;
import org.aergia.vinny.engine.EngineImpl;
import org.aergia.vinny.engine.VinnyConfiguration;
import org.aergia.vinny.gui.controller.VinnyController;
import org.aergia.vinny.gui.controller.VinnyControllerImpl;
import org.aergia.vinny.gui.util.DialogUtil;
import org.aergia.vinny.gui.util.InitializeDatabase;
import org.aergia.vinny.gui.view.InitializeLocation;
import org.aergia.vinny.gui.view.VinnyFrame;
import org.aergia.vinny.util.DateUtil;

/**
 * @author Fred
 */
public class VinnyMain {

	public static final String PREFERRED_LOOK_AND_FEEL = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";

	/**
	 * @param args
	 */
	public static void main(final String[] args) {
		try {
			final InputStream is = VinnyMain.class.getResourceAsStream("/META-INF/logging.properties");
			LogManager.getLogManager().readConfiguration(is);
		} catch (final Exception e) {
			e.printStackTrace();
		}

		String persistenceUnit = "vinny";
		if (args.length > 0) {
			persistenceUnit = args[0];
		}

		final VinnyMain main = new VinnyMain(persistenceUnit);
		main.initialize(persistenceUnit);

	}

	private final String configurationFileName;
	private VinnyController controller;
	private Engine engine;
	private VinnyFrame frame;
	private final Logger logger;

	public VinnyMain(final String persistenceUnit) {
		try {
			new File(System.getProperty("user.home") + "/.vinny/logs").mkdirs();
		} catch (final Exception e) {
			// ignore
		}
		this.logger = Logger.getLogger(VinnyMain.class.getCanonicalName());
		this.logger.info("Vinny application starting - " + persistenceUnit);
		this.configurationFileName = System.getProperty("user.home") + "/.vinny/conf/cfg.xml";
		try {
			UIManager.setLookAndFeel(VinnyMain.PREFERRED_LOOK_AND_FEEL);
		} catch (final Exception e) {
			// empty
		}

	}

	private void determineWhereWeAreAt(final VinnyConfiguration cfg) {
		try {
			final CountDownLatch latch = new CountDownLatch(1);
			final InitializeDatabase initDatabaseFrame = new InitializeDatabase(cfg, latch);
			initDatabaseFrame.setVisible(true);
			latch.await();
			initDatabaseFrame.setVisible(false);
		} catch (final InterruptedException ie) {
			this.logger.log(Level.SEVERE, "Determining where we are at was interrupted.");
		}
	}

	private void initialize(final String persistenceUnit) {

		try {

			final VinnyConfiguration cfg = new VinnyConfiguration();

			if (!new File(this.configurationFileName).exists()) {
				this.determineWhereWeAreAt(cfg);
				cfg.saveConfigurationFile(this.configurationFileName);
			} else {
				cfg.loadConfigurationFile(this.configurationFileName);
			}

			final Map<String, Object> properties = new HashMap<String, Object>();
			properties.put("openjpa.ConnectionURL", cfg.getDatabaseUrl(persistenceUnit));
			properties.put("openjpa.ConnectionUserName", cfg.getUserName(persistenceUnit));
			properties.put("openjpa.ConnectionPassword", cfg.getUserPassword(persistenceUnit));

			final EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnit, properties);
			final EntityManager em = emf.createEntityManager();
			final Database database = new DatabaseImpl(em);

			BackupUtil.backupDatabase(database);

			this.engine = new EngineImpl(database);

			int locationId = cfg.getLocationId(persistenceUnit);

			if (!this.engine.isDatabaseInitialized() && (locationId == 0)) {
				locationId = this.initializeLocation(cfg, persistenceUnit);
			}
			final String productName = this.engine.getOption(VinnyOption.PRODUCT_NAME, Engine.DEFAULT_PRODUCT_NAME);

			Location location = null;

			if (locationId == 0) {
				DialogUtil.alert(null, "Error connecting to database.  Please try again.", "Database error");
				System.exit(-1);
			} else {
				final ApiResult<Location> locResult = this.engine.getLocation(locationId);
				if (!locResult.isError()) {
					location = locResult.getResult();
				} else {
					// Select an existing location or create a new one.
				}
			}

			if (location == null) {
				DialogUtil.alert(null, "Invalid location in database.", "Invalid Location");
				System.exit(-1);
				return;
			}

			this.engine.setLocation(location);

			Workstation workstation = null;
			final int workstationId = cfg.getWorkstationId(persistenceUnit);

			ApiResult<Workstation> workstationResult = null;
			if (workstationId == 0) {
				String hostName = Long.toString(System.currentTimeMillis());
				try {
					hostName = InetAddress.getLocalHost().getHostName();
				} catch (final Exception e) {
					// ignore
				}
				workstationResult = this.engine.installWorkstation("workstation=" + hostName);
				if (!workstationResult.isError()) {
					cfg.setWorkstationId(persistenceUnit, workstationResult.getResult().getId());
					cfg.saveConfigurationFile(this.configurationFileName);
				}
			} else {
				workstationResult = this.engine.getWorkstation(workstationId);
				if (!workstationResult.isError()) {
					workstation = workstationResult.getResult();
				}
			}

			if (workstationResult.isError()) {
				DialogUtil.alert(null, "Invalid workstation in database.", "Invalid Workstation");
				System.exit(-1);
				return;
			}
			workstation = workstationResult.getResult();

			this.engine.setWorkstation(workstation);

			this.controller = new VinnyControllerImpl(this.engine);
			this.frame = new VinnyFrame(this.controller, productName, location.getName());
			this.frame.setVisible(true);
		} catch (final PersistenceException pe) {
			this.logger.log(Level.SEVERE, "Could not connect to the database. " + pe.getMessage());
			DialogUtil.alert(null, "Could not connected to the database.", "Database error.");
			System.exit(-3);
		}
	}

	private int initializeLocation(final VinnyConfiguration cfg, final String persistenceUnit) {

		final InitializeLocation dialog = new InitializeLocation();
		dialog.setVisible(true);
		final String orgName = dialog.getOrganizationName();
		final Organization org = new Organization(orgName);
		final String locName = dialog.getLocationName();
		final Location location = new Location(locName, org);
		final int rate = dialog.getRate();
		final Collection<OperatingHours> operatingHours = new ArrayList<OperatingHours>();

		for (final DayOfWeek day : DayOfWeek.values()) {
			final int startTime = dialog.getStartTime(day);
			final int endTime = dialog.getEndTime(day);
			operatingHours.add(new OperatingHours(day, startTime, endTime));
		}

		final ApiResult<Location> locResult = this.engine.installLocation(org, location, operatingHours, rate, DateUtil.getInstance());
		if (!locResult.isError()) {
			cfg.addPersistenceUnit(persistenceUnit);
			cfg.setLocationId(persistenceUnit, locResult.getResult().getId());
			cfg.setServerName(persistenceUnit, "localhost");
			cfg.saveConfigurationFile(this.configurationFileName);
			return locResult.getResult().getId();
		}

		return 0;
	}
}
